package com.tools.web.interceptor.auth;

import com.fasterxml.jackson.core.type.TypeReference;
import com.tools.common.object.json.JsonKit;
import com.tools.common.object.Note;
import com.tools.common.object.string.StrKit;
import com.tools.common.object.string.VerifyKit;
import com.tools.common.thread.OwnThreadFactory;
import com.tools.common.thread.Timeunit;
import com.tools.web.interceptor.access.AccessManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 用于 “同用户多端在线的用户认证” 的管理器
 * */
@Note("用于 “同用户多端在线的用户认证” 的管理器")
public final class MultipleLocalAuthenticationManager extends GenericAuthenticationManager {

    @Note("缓存容器")
    private final ConcurrentHashMap<String, String> cache;

    @Note("定时轮询的线程池，用于清理已过期的条目")
    private final ScheduledThreadPoolExecutor timePollingTor;

    @Note("设备类型 ID 与这种设备能同时在线的最大数量")
    private final Map<String, Integer> deviceTypeIdAndMaxCount;

    @Note("日志对象")
    private static final Logger LOGGER = LoggerFactory.getLogger(MultipleLocalAuthenticationManager.class);

    @Note("日志输出的字符串边框")
    public static final String BORDER = StrKit.addString("=", 100);

    /* **************************************************************************************
     *
     *          构造器
     *
     * **************************************************************************************
     * */

    public MultipleLocalAuthenticationManager(long execTime, TimeUnit execTimeUnit, AuthSource source,
                                              AuthUserService authUserService) {
        this(execTime, execTimeUnit, source, authUserService, null);
    }


    public MultipleLocalAuthenticationManager(long execTime, TimeUnit execTimeUnit, AuthSource source,
                                              AuthUserService authUserService, AccessManager accessManager) {
        super(source, authUserService, accessManager);
        String json = source.getDeviceTypeIdAndMaxTotalJsonString();
        if(!VerifyKit.isJsonObject(json)) {
            throw new IllegalArgumentException(json + " 不是 JSON 对象类型的字符串");
        }
        this.deviceTypeIdAndMaxCount = JsonKit.toEntity(json, new TypeReference<HashMap<String, Integer>>() {});
        this.protected_checkDeviceTypeIdAndMaxCountMap(this.deviceTypeIdAndMaxCount);
        this.cache = new ConcurrentHashMap<>();
        OwnThreadFactory schedulerFactory = new OwnThreadFactory("authUser-cache-clean-", true);
        this.timePollingTor = new ScheduledThreadPoolExecutor(1, schedulerFactory);
        //开启定时轮询，清除不使用且已经过期的 key-value。默认 30 分钟轮询一次
        this.timePollingTor.scheduleAtFixedRate(this::private_timedPolling,
                execTime,
                execTime,
                execTimeUnit
        );
        LOGGER.info("AuthUserLocalCache 用户记录缓存实例 {} 创建成功，定时轮询时间为: {} {}",
                Integer.toHexString(this.hashCode()), execTime, execTimeUnit);
    }

    /* **************************************************************************************
     *
     *          功能方法
     *
     * **************************************************************************************
     * */

    @Note("从缓存中获取 MultiClientAuthUser 对象")
    protected Res getAuthUser(String key, AuthToken token) {
        synchronized (key) {
            String json = this.cache.get(key);
            if(json == null) {
                return super.protected_getResIfExpired(token);
            }
            MultiAuthUser authUser = JsonKit.toEntity(json, MultiAuthUser.class);
            ConcurrentHashMap<String, LinkedHashMap<String, Long>> deviceAndLoginRecord = authUser.getDeviceAndLoginRecord();
            if(deviceAndLoginRecord == null || deviceAndLoginRecord.isEmpty()) {
                return super.protected_getResIfExpired(token);
            }
            String deviceTypeId = token.getDeviceTypeId();
            String uuid = token.getUuid();
            Map<String, Long> record = deviceAndLoginRecord.get(deviceTypeId);
            if(record == null || record.isEmpty()) {
                deviceAndLoginRecord.remove(deviceTypeId);
                if(deviceAndLoginRecord.isEmpty()) {
                    this.cache.remove(key);
                }
                return super.protected_getResIfExpired(token);
            }
            Long expiration = record.get(uuid);
            if(expiration == null || expiration <= System.currentTimeMillis()) {
                record.remove(uuid);
                if(record.isEmpty()) {
                    deviceAndLoginRecord.remove(deviceTypeId);
                    if(deviceAndLoginRecord.isEmpty()) {
                        this.cache.remove(key);
                    }
                }
                return super.protected_getResIfExpired(token);
            }
            MultiAuthUser newAuthUser = authUser.cloneAuthUser();
            newAuthUser.setUuid(uuid);
            newAuthUser.setDeviceTypeId(deviceTypeId);
            newAuthUser.setUserType(token.getUserType());
            return new Res(true, newAuthUser);
        }
    }

    @Note("将 MultiClientAuthUser 对象放入缓存中，并设置过期时间")
    protected void setAuthUser(String key, AuthUser srcAuthUser, AuthToken token) {
        synchronized (key) {
            MultiAuthUser authUser = (MultiAuthUser) srcAuthUser;
            String json = this.cache.get(key);
            if(json == null) {
                //添加第一个
                MultiAuthUser newAuthUser = authUser.cloneAuthUser();
                ConcurrentHashMap<String, LinkedHashMap<String, Long>> deviceAndLoginRecord = newAuthUser.getDeviceAndLoginRecord();
                LinkedHashMap<String, Long> loginRecord = new LinkedHashMap<>(2);
                loginRecord.put(token.getUuid(), token.getExpiration());
                deviceAndLoginRecord.put(token.getDeviceTypeId(), loginRecord);
                String newAuthUserJson = JsonKit.toJSON(newAuthUser);
                this.cache.put(key, newAuthUserJson);
                return;
            }
            MultiAuthUser oldAuthUser = JsonKit.toEntity(json, MultiAuthUser.class);
            //已有了
            authUser.copyBaseUserDataTo(oldAuthUser);
            authUser.copyCustomDataMapTo(oldAuthUser);
            ConcurrentHashMap<String, LinkedHashMap<String, Long>> deviceAndLoginRecord = oldAuthUser.getDeviceAndLoginRecord();
            String deviceTypeId = token.getDeviceTypeId();
            String uuid = token.getUuid();
            long expirationMill = token.getExpiration();
            LinkedHashMap<String, Long> record = deviceAndLoginRecord.computeIfAbsent(deviceTypeId,
                    k1 -> new LinkedHashMap<>(2));
            //没到最大限制数量
            Integer maxSize = this.deviceTypeIdAndMaxCount.get(deviceTypeId);
            if(record.size() < maxSize) {
                record.put(uuid, expirationMill);
                deviceAndLoginRecord.put(deviceTypeId, record);
                return;
            }
            //已经到了，强迫移除第一个
            String firstKey = record.keySet().iterator().next();
            record.remove(firstKey);
            record.put(uuid, expirationMill);
            deviceAndLoginRecord.put(deviceTypeId, record);
        }
    }

    @Note("将指定的用户过期")
    protected void removeAuthUser(String key) {
        synchronized (key) {
            this.cache.remove(key);
        }
    }

    @Note("更新用户数据对象")
    protected void updateAuthUser(String key, AuthUser authUser, String separator) {
        synchronized (key) {
            MultiAuthUser newAuthUser = (MultiAuthUser) authUser;
            String json = this.cache.get(key);
            if(json == null) return;
            MultiAuthUser oldAuthUser = JsonKit.toEntity(json, MultiAuthUser.class);
            newAuthUser.copyBaseUserDataTo(oldAuthUser);
            newAuthUser.copyCustomDataMapTo(oldAuthUser);
            String newAuthUserJson = JsonKit.toJSON(newAuthUser);
            this.cache.put(key, newAuthUserJson);
        }
    }


    @Override
    protected Logger protected_getLogger() {
        return LOGGER;
    }

    @Override
    protected Timeunit protected_getTimeunitToBeUsed() {
        return Timeunit.MILL;
    }

    @Override
    protected void protected_loginOut(String key, String deviceTypeId, String uuid,
                                      AuthUser authUser, String separator) {
        synchronized (key) {
            MultiAuthUser multiAuthUser = (MultiAuthUser) authUser;
            ConcurrentHashMap<String, LinkedHashMap<String, Long>> deviceAndLoginRecord = multiAuthUser.getDeviceAndLoginRecord();
            if(deviceAndLoginRecord.isEmpty()) {
                this.cache.remove(key);
                return;
            }
            Map<String, Long> loginRecord = deviceAndLoginRecord.get(deviceTypeId);
            if(loginRecord == null || loginRecord.isEmpty()) {
                deviceAndLoginRecord.remove(deviceTypeId);
                if(deviceAndLoginRecord.isEmpty()) {
                    this.cache.remove(key);
                }
                return;
            }
            loginRecord.remove(uuid);
            if(loginRecord.isEmpty()) {
                deviceAndLoginRecord.remove(deviceTypeId);
                if(deviceAndLoginRecord.isEmpty()) {
                    this.cache.remove(key);
                }
            }
        }
    }

    /* **************************************************************************************
     *
     *          私有逻辑
     *
     * **************************************************************************************
     * */

    @Note("清理过期的条目")
    private void private_timedPolling() {
        String name = Integer.toHexString(this.hashCode());
        if(this.cache.isEmpty()) {
            LOGGER.info("MultiClientAuthUser 缓存 {} 的定时轮询任务开始执行，检测到其内容为空，轮询任务结束!", name);
            return;
        }
        LOGGER.info("MultiClientAuthUser 缓存实例 {} 的定时轮询任务开始执行，准备批量移除已过期的登录用户...", name);
        StringBuilder expire = new StringBuilder();
        StringBuilder residual = new StringBuilder();
        Set<Map.Entry<String, String>> entries = this.cache.entrySet();
        for (Map.Entry<String, String> e : entries) {
            String key = e.getKey();
            synchronized (key) {
                String json = this.cache.get(key);
                if(json == null) continue;
                MultiAuthUser authUser = JsonKit.toEntity(json, MultiAuthUser.class);
                ConcurrentHashMap<String, LinkedHashMap<String, Long>> deviceAndLoginRecord = authUser.getDeviceAndLoginRecord();
                if(deviceAndLoginRecord == null || deviceAndLoginRecord.isEmpty()) {
                    this.private_appendEntryLog(expire, key, authUser);
                    this.cache.remove(key);
                    continue;
                }
                for(Map.Entry<String, LinkedHashMap<String, Long>> ep : deviceAndLoginRecord.entrySet()) {
                    String deviceTypeId = ep.getKey();
                    Map<String, Long> record = ep.getValue();
                    if(record == null || record.isEmpty()) {
                        deviceAndLoginRecord.remove(deviceTypeId);
                        this.private_appendEntryLog(expire, key, deviceTypeId, authUser);
                        if(deviceAndLoginRecord.isEmpty()) {
                            this.private_appendEntryLog(expire, key, authUser);
                            this.cache.remove(key);
                        }
                        continue;
                    }
                    for(Map.Entry<String, Long> ec : record.entrySet()) {
                        String uuid = ec.getKey();
                        Long expiration = ec.getValue();
                        if(expiration == null || expiration <= System.currentTimeMillis()) {
                            record.remove(uuid);
                            this.private_appendEntryLog(expire, key,
                                    deviceTypeId, uuid, authUser);
                            if(record.isEmpty()) {
                                deviceAndLoginRecord.remove(deviceTypeId);
                                this.private_appendEntryLog(expire, key, deviceTypeId, authUser);
                                if(deviceAndLoginRecord.isEmpty()) {
                                    this.private_appendEntryLog(expire, key, authUser);
                                    this.cache.remove(key);
                                }
                            }
                        } else {
                            residual.append('【')
                                    .append(key).append(':').append(deviceTypeId).append(':')
                                    .append(uuid).append("】 =-=> ")
                                    .append(authUser).append('\n');
                        }
                    }
                }
            }
        }
        LOGGER.info("MultiClientAuthUser 缓存 {} 定时轮询任务结束，共移除以下条目: \n{}=================={}\n{}{}=================={}",
                this, BORDER, BORDER, expire, BORDER, BORDER
        );
        LOGGER.info("MultiClientAuthUser 缓存实例 {} 当前缓存剩余条目: \n{}=================={}\n{}{}=================={}",
                this, BORDER, BORDER, residual, BORDER, BORDER);
    }


    @Note("当 MultiClientAuthUser 整个的所有记录都为空时的日志")
    private void private_appendEntryLog(StringBuilder builder, String key, MultiAuthUser authUser) {
        builder.append('【')
                .append(key).append("】 不存在或已为空 =-=> ").append(authUser).append('\n');
    }

    @Note("当某个设备类型的所有记录整个都为空时的日志")
    private void private_appendEntryLog(StringBuilder builder, String key,
                                            String deviceTypeId, MultiAuthUser authUser) {
        builder.append('【')
                .append(key).append(':')
                .append(deviceTypeId).append("】 不存在或已为空 =-=> ")
                .append(authUser).append('\n');
    }

    @Note("当某个设备类型的某条记录过期的日志")
    private void private_appendEntryLog(StringBuilder builder, String key,
                                            String deviceTypeId, String uuid,
                                            MultiAuthUser authUser) {
        builder.append('【')
                .append(key).append(':').append(deviceTypeId).append(':')
                .append(uuid).append("】 =-=> ")
                .append(authUser).append('\n');
    }



}
